/*
	BASS "live" spectrum analyser example
	Copyright (c) 2002-2010 Un4seen Developments Ltd.
*/

#define BASS_CONFIG_REC_LOOPBACK 28 

#include <windows.h>
#include <stdio.h>
#include <math.h>
#include "basswasapi.h"
#include "bass.h"

#define SPECWIDTH 368	// display width
#define SPECHEIGHT 127	// height (changing requires palette adjustments too)

HWND win=NULL;
DWORD timer=0;
static DWORD rcounter=0;

HRECORD chan;	// recording channel

HDC specdc=0;
HBITMAP specbmp=0;
BYTE *specbuf;

int audio_device=(-1);

int specmode=0,specpos=0; // spectrum mode (and marker pos for 2nd mode)
int spectrum_width=16;

// display error messages
void Error(const char *es)
{
	char mes[200];
	sprintf(mes,"%s\n(error code: %d)",es,BASS_ErrorGetCode());
	MessageBox(win,mes,0,0);
}

// display error messages
void Errori(const char *es, DWORD i)
{
	char mes[200];
	sprintf(mes,"%s\n return=%d\n (error code: %d)",es,i,BASS_ErrorGetCode());
	MessageBox(win,mes,0,0);
}
static int cval=0;
// update the spectrum display - the interesting bit :)
void CALLBACK UpdateSpectrum(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
	static DWORD quietcount=0;
	HDC dc;
	int x,y,y1;
	char str[80];
	DWORD ires;

	  	  float fft[2048]; // 1024 fft values returned *2
	  	  int i;
	  	  for (i=0; i<2048;i++) fft[i]=0.0;
	  	  y1=0;
    	  BASS_WASAPI_SetDevice(audio_device); // set device context to input/loopback device ****
     	  ires=BASS_WASAPI_GetData(fft,BASS_DATA_FFT2048 ); // get the FFT data
     	  if ((ires!=0) && (ires!=16384)) { Errori("GetData ",ires); return; }
		  if (!specmode) 
          { // "normal" FFT
			memset(specbuf,0,SPECWIDTH*SPECHEIGHT);
			for (x=0;x<SPECWIDTH/2;x++) 
            {
				y=sqrt(fft[x+1])*3*SPECHEIGHT-4; // scale it (sqrt to make low values more visible)
				if (y<0) y=(0-y);
                if (y>SPECHEIGHT) y=SPECHEIGHT; // cap it
				if (x && (y1=(y+y1)/2)) // interpolate from previous to make the display smoother
					while (--y1>=0) specbuf[y1*SPECWIDTH+x*2-1]=y1+1;
				y1=y;
				while (--y>=0) specbuf[y*SPECWIDTH+x*2]=y+1; // draw level
			}
		  } 
			int b0=0; int maxy=0;

			memset(specbuf,0,SPECWIDTH*SPECHEIGHT);
			for (x=0;x<spectrum_width;x++) 
            {
				float peak=0;
				int b1=pow(2,x*10.0/(spectrum_width-1));
				if (b1>1023) b1=1023;
				if (b1<=b0) b1=b0+1; // make sure it uses at least 1 FFT bin
				for (;b0<b1;b0++)
					if (peak<fft[1+b0]) peak=fft[1+b0];
				y=sqrt(peak)*3*SPECHEIGHT-4; // scale it (sqrt to make low values more visible)
				if (y>SPECHEIGHT) y=SPECHEIGHT; // cap it
				while (--y>=0)
					memset(specbuf+y*SPECWIDTH+x*(SPECWIDTH/spectrum_width),y+1,0.9*(SPECWIDTH/spectrum_width)); // draw bar
				if (y>maxy) maxy=y;
			}
	// update the display
	dc=GetDC(win);
	BitBlt(dc,0,0,SPECWIDTH,SPECHEIGHT,specdc,0,0,SRCCOPY);
	if (LOWORD(BASS_WASAPI_GetLevel(audio_device))<500) { // check if it's quiet
		quietcount++;
		if (quietcount>40 && (quietcount&16)) { // it's been quiet for over a second
			RECT r={0,0,SPECWIDTH,SPECHEIGHT};
			SetTextColor(dc,0xffffff);
			SetBkMode(dc,TRANSPARENT);
			DrawText(dc,"make some noise!",-1,&r,DT_CENTER|DT_VCENTER|DT_SINGLELINE);
		}
	} else
		quietcount=0; // not quiet
	ReleaseDC(win,dc);
}

// Recording callback - not doing anything with the data
DWORD CALLBACK DuffRecording(void *buffer, DWORD length, void *user)
{   rcounter=length;
	return rcounter; // continue recording
}

// window procedure
long FAR PASCAL SpectrumWindowProc(HWND h, UINT m, WPARAM w, LPARAM l)
{
	switch (m) {
		case WM_PAINT:
			if (GetUpdateRect(h,0,0)) {
				PAINTSTRUCT p;
				HDC dc;
				if (!(dc=BeginPaint(h,&p))) return 0;
				BitBlt(dc,0,0,SPECWIDTH,SPECHEIGHT,specdc,0,0,SRCCOPY);
				EndPaint(h,&p);
			}
			return 0;

		case WM_LBUTTONUP:
			specmode=(specmode+1)%4; // swap spectrum mode
			memset(specbuf,0,SPECWIDTH*SPECHEIGHT);	// clear display
			return 0;

		case WM_CREATE:
			win=h;
			DWORD i; char str[80];     
            BASS_WASAPI_DEVICEINFO info;

            for (i=0; BASS_WASAPI_GetDeviceInfo(i, &info); i++)
               { 
                if (
                  (info.type==BASS_WASAPI_TYPE_SPEAKERS)
                && (info.flags&BASS_DEVICE_INPUT) // device is an loopback device (not input)
                      && (info.flags&BASS_DEVICE_LOOPBACK)
                      && (info.flags&BASS_DEVICE_ENABLED)
                      ) // and it is enabled
                         { audio_device=i; 
                         break;
                         }}

			// initialize BASS recording (default device)
              if (!BASS_Init(0, 44100, 0, h, NULL))
				{ Error("Can't initialize device");
				return -1;
			}
              
              if(!BASS_WASAPI_Init(audio_device,44100,2,BASS_WASAPI_BUFFER ,0.5,0,&DuffRecording,NULL))
				{ Error("Can't initialize device");
				return -1;
			}
              BASS_WASAPI_Init(audio_device-1,0,0,0,0.5,0,NULL,NULL); // initialize corresponding output device ****
              BASS_WASAPI_SetDevice(audio_device); // set device context back to input/loopback device ****
			// start recording (44100hz mono 16-bit)
			if (!(BASS_WASAPI_Start())) {
				Error("Can't start recording");
				return -1;
			}

			{ // create bitmap to draw spectrum in (8 bit for easy updating)
				BYTE data[2000]={0};
				BITMAPINFOHEADER *bh=(BITMAPINFOHEADER*)data;
				RGBQUAD *pal=(RGBQUAD*)(data+sizeof(*bh));
				int a;
				bh->biSize=sizeof(*bh);
				bh->biWidth=SPECWIDTH;
				bh->biHeight=SPECHEIGHT; // upside down (line 0=bottom)
				bh->biPlanes=1;
				bh->biBitCount=8;
				bh->biClrUsed=bh->biClrImportant=256;
				// setup palette
				for (a=1;a<128;a++) {
					pal[a].rgbGreen=256-2*a;
					pal[a].rgbRed=2*a;
				}
				for (a=0;a<32;a++) {
					pal[128+a].rgbBlue=8*a;
					pal[128+32+a].rgbBlue=255;
					pal[128+32+a].rgbRed=8*a;
					pal[128+64+a].rgbRed=255;
					pal[128+64+a].rgbBlue=8*(31-a);
					pal[128+64+a].rgbGreen=8*a;
					pal[128+96+a].rgbRed=255;
					pal[128+96+a].rgbGreen=255;
					pal[128+96+a].rgbBlue=8*a;
				}
				// create the bitmap
				specbmp=CreateDIBSection(0,(BITMAPINFO*)bh,DIB_RGB_COLORS,(void**)&specbuf,NULL,0);
				specdc=CreateCompatibleDC(0);
				SelectObject(specdc,specbmp);
			}

			// setup update timer (40hz)
			timer=timeSetEvent(25,25,(LPTIMECALLBACK)&UpdateSpectrum,0,TIME_PERIODIC);
			break;

		case WM_DESTROY:
			if (timer) timeKillEvent(timer);
			BASS_WASAPI_Free();
			BASS_WASAPI_Free(); // release the 2nd device too ****
			if (specdc) DeleteDC(specdc);
			if (specbmp) DeleteObject(specbmp);
			PostQuitMessage(0);
			break;
	}
	return DefWindowProc(h, m, w, l);
}

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow)
{
	WNDCLASS wc={0};
    MSG msg; char str[256];

	// check the correct BASS was loaded
	if (HIWORD(BASS_GetVersion())!=BASSVERSION) {
		MessageBox(0,"An incorrect version of BASS.DLL was loaded",0,MB_ICONERROR);
		return 0;
	}

	// register window class and create the window
	wc.lpfnWndProc = SpectrumWindowProc;
	wc.hInstance = hInstance;
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.lpszClassName = "BASS-Spectrum";
	if (!RegisterClass(&wc) || !CreateWindow("BASS-Spectrum",
			"\"Live\" spectrum display",
			WS_POPUPWINDOW|WS_CAPTION|WS_VISIBLE, 200, 200,
			SPECWIDTH+2*GetSystemMetrics(SM_CXDLGFRAME),
			SPECHEIGHT+GetSystemMetrics(SM_CYCAPTION)+2*GetSystemMetrics(SM_CYDLGFRAME),
			NULL, NULL, hInstance, NULL)) {
		Error("Can't create window");
		return 0;
	}
	ShowWindow(win, SW_SHOWNORMAL);

	while (GetMessage(&msg,NULL,0,0)>0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return 0;
}
